/*
 * COPYRIGHT:       See COPYING in the top level directory
 * PROJECT:         ReactOS Kernel Streaming
 * FILE:            drivers/wdm/audio/legacy/stream/driver.c
 * PURPOSE:         WDM Codec Class Driver
 * PROGRAMMER:      Johannes Anderwald
 */

#include "stream.h"

NTSTATUS
NTAPI
StreamClassAddDevice(
    IN PDRIVER_OBJECT  DriverObject,
    IN PDEVICE_OBJECT  PhysicalDeviceObject)
{
    PSTREAM_CLASS_DRIVER_EXTENSION DriverObjectExtension;
    PDEVICE_OBJECT DeviceObject, LowerDeviceObject;
    PSTREAM_DEVICE_EXTENSION DeviceExtension;
    PKSOBJECT_CREATE_ITEM ItemList;
    NTSTATUS Status;

    /* Fetch driver object extension */
    DriverObjectExtension = IoGetDriverObjectExtension(DriverObject, (PVOID)StreamClassAddDevice);
    if (!DriverObjectExtension)
    {
        /* Failed to get driver extension */
        return STATUS_DEVICE_DOES_NOT_EXIST;
    }
    /* Allocate Create Item */
    ItemList = ExAllocatePool(NonPagedPool, sizeof(KSOBJECT_CREATE_ITEM));
    if (!ItemList)
    {
        /* Failed to allocated Create Item */
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    /* Create the FDO */
    Status = IoCreateDevice(DriverObject, DriverObjectExtension->Data.DeviceExtensionSize + sizeof(STREAM_DEVICE_EXTENSION), NULL, FILE_DEVICE_KS, FILE_AUTOGENERATED_DEVICE_NAME | FILE_DEVICE_SECURE_OPEN, 0, &DeviceObject);
    if (!NT_SUCCESS(Status))
    {
        /* Failed to create the FDO */
        ExFreePool(ItemList);
        return Status;
    }

    /* Attach to device stack */
    LowerDeviceObject = IoAttachDeviceToDeviceStack(DeviceObject, PhysicalDeviceObject);
    if (!LowerDeviceObject)
    {
        /* Failed to attach */
        IoDeleteDevice(DeviceObject);
        ExFreePool(ItemList);
        return STATUS_UNSUCCESSFUL;
    }

    /* Zero Create item */
    RtlZeroMemory(ItemList, sizeof(KSOBJECT_CREATE_ITEM));
    /* Setup object class */
    RtlInitUnicodeString(&ItemList->ObjectClass, L"GLOBAL");
    /* Setup CreateDispatch routine */
    ItemList->Create = StreamClassCreateFilter;

    /* Get device extension */
    DeviceExtension = (PSTREAM_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
    /* Zero device extension */
    RtlZeroMemory(DeviceExtension, sizeof(STREAM_DEVICE_EXTENSION));
    /* Initialize Ks streaming */
    Status = KsAllocateDeviceHeader(&DeviceExtension->Header, 1, ItemList);
    if (!NT_SUCCESS(Status))
    {
        /* Cleanup resources */
        IoDetachDevice(LowerDeviceObject);
        IoDeleteDevice(DeviceObject);
        ExFreePool(ItemList);
        return Status;
    }

    /* Store lower device object */
    DeviceExtension->LowerDeviceObject = LowerDeviceObject;

    /* Store physical device object */
    DeviceExtension->PhysicalDeviceObject = PhysicalDeviceObject;
    /* Store driver object extension */
    DeviceExtension->DriverExtension = DriverObjectExtension;
    /* Initialize memory list */
    InitializeListHead(&DeviceExtension->MemoryResourceList);
    /* Setup device extension */
    DeviceExtension->DeviceExtension = (PVOID) (DeviceExtension + 1);
    /* Init interrupt dpc */
    KeInitializeDpc(&DeviceExtension->InterruptDpc, StreamClassInterruptDpc, (PVOID)DeviceExtension);

    /* Set device transfer method */
    DeviceObject->Flags |= DO_DIRECT_IO | DO_POWER_PAGABLE;
    /* Clear init flag */
    DeviceObject->Flags &= ~ DO_DEVICE_INITIALIZING;

    return Status;
}

/*
 *@implemented
 */
NTSTATUS
STREAMAPI
StreamClassRegisterAdapter(
    IN PVOID Argument1,
    IN PVOID Argument2,
    IN PHW_INITIALIZATION_DATA HwInitializationData)
{
    NTSTATUS Status;
    PSTREAM_CLASS_DRIVER_EXTENSION DriverObjectExtension;
    PDRIVER_OBJECT DriverObject = (PDRIVER_OBJECT)Argument1;

    /* Allocate driver extension */
    Status = IoAllocateDriverObjectExtension(DriverObject, (PVOID)StreamClassAddDevice, sizeof(STREAM_CLASS_DRIVER_EXTENSION), (PVOID*)&DriverObjectExtension);
    if (!NT_SUCCESS(Status))
    {
        /* Failed to allocate */
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    /* Zero driver object extension */
    RtlZeroMemory(DriverObjectExtension, sizeof(STREAM_CLASS_DRIVER_EXTENSION));

    /* copy HwInitializationData */
    RtlCopyMemory(&DriverObjectExtension->Data, HwInitializationData, sizeof(HW_INITIALIZATION_DATA));

    /* Setup device init methods */
    DriverObject->DriverExtension->AddDevice = StreamClassAddDevice;
    DriverObject->DriverUnload = KsNullDriverUnload;

    /* Setup irp handlers */
    DriverObject->MajorFunction[IRP_MJ_PNP] = StreamClassPnp;
    DriverObject->MajorFunction[IRP_MJ_POWER] = StreamClassPower;
    DriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] = StreamClassSystemControl;
    DriverObject->MajorFunction[IRP_MJ_CLEANUP] = StreamClassCleanup;
    DriverObject->MajorFunction[IRP_MJ_FLUSH_BUFFERS] = StreamClassFlushBuffers;
    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = StreamClassDeviceControl;

    /* Let Ks handle these */
    KsSetMajorFunctionHandler(DriverObject, IRP_MJ_CREATE);
    KsSetMajorFunctionHandler(DriverObject, IRP_MJ_CLOSE);

    return STATUS_SUCCESS;
}

/*
 *@implemented
 */

VOID
NTAPI
StreamClassReenumerateStreams(
    IN PVOID  HwDeviceExtension,
    IN ULONG  StreamDescriptorSize)
{
    HW_STREAM_REQUEST_BLOCK_EXT RequestBlock;
    PSTREAM_DEVICE_EXTENSION DeviceExtension;
    PHW_STREAM_DESCRIPTOR StreamDescriptor;

    if (!HwDeviceExtension || !StreamDescriptorSize)
        return;

    StreamDescriptor = ExAllocatePool(NonPagedPool, StreamDescriptorSize);
    if (!StreamDescriptor)
        return;

    /* Zero stream descriptor */
    RtlZeroMemory(StreamDescriptor, StreamDescriptorSize);

    /* Get our DeviceExtension */
    DeviceExtension = (PSTREAM_DEVICE_EXTENSION) ((ULONG_PTR)HwDeviceExtension - sizeof(STREAM_DEVICE_EXTENSION));
    ASSERT(DeviceExtension->DeviceExtension == HwDeviceExtension);


    /* Zero RequestBlock */
    RtlZeroMemory(&RequestBlock, sizeof(HW_STREAM_REQUEST_BLOCK_EXT));

    /* Setup get stream info struct */
    RequestBlock.Block.SizeOfThisPacket = sizeof(HW_STREAM_REQUEST_BLOCK);
    RequestBlock.Block.Command = SRB_GET_STREAM_INFO;
    RequestBlock.Block.CommandData.StreamBuffer = StreamDescriptor;
    KeInitializeEvent(&RequestBlock.Event, SynchronizationEvent, FALSE);

    /* FIXME SYNCHRONIZATION */

    /* Send the request */
    DeviceExtension->DriverExtension->Data.HwReceivePacket ((PHW_STREAM_REQUEST_BLOCK)&RequestBlock);

    /* Is the device already completed? */
    if (RequestBlock.Block.Status == STATUS_PENDING)
    {
        /* Request is pending, wait for result */
        KeWaitForSingleObject(&RequestBlock.Event, Executive, KernelMode, FALSE, NULL);
    }

    if (!NT_SUCCESS(RequestBlock.Block.Status))
    {
        /* Release Stream descriptor */
        ExFreePool(StreamDescriptor);
    }
    else
    {
        if (DeviceExtension->StreamDescriptor)
        {
            /* Release old stream descriptor */
            ExFreePool(DeviceExtension->StreamDescriptor);
        }

        /* Store stream descriptor */
        DeviceExtension->StreamDescriptor = StreamDescriptor;
        DeviceExtension->StreamDescriptorSize = StreamDescriptorSize;
    }

}

/*
 *@implemented
 */

VOID
NTAPI
StreamClassDebugAssert(
    IN PCHAR File,
    IN ULONG Line,
    IN PCHAR AssertText,
    IN ULONG AssertValue)
{
#if DBG
    DbgBreakPoint();
#endif
}

/*
 *@implemented
 */
VOID
__cdecl
StreamClassDebugPrint(
    IN STREAM_DEBUG_LEVEL DebugPrintLevel,
    IN PCCHAR DebugMessage,
    ...)
{
#if DBG
    va_list ap;

    if (DebugPrintLevel <=STREAMDEBUG_LEVEL)
    {
        va_start(ap, DebugMessage);

        DbgPrint(DebugMessage, ap);

        va_end(ap);
    }
#endif

}

/*
 *@unimplemented
 */
VOID
__cdecl
StreamClassDeviceNotification(
    IN STREAM_MINIDRIVER_DEVICE_NOTIFICATION_TYPE  NotificationType,
    IN PVOID  HwDeviceExtension,
    IN PHW_STREAM_REQUEST_BLOCK  pSrb,
    IN PKSEVENT_ENTRY  EventEntry,
    IN GUID  *EventSet,
    IN ULONG  EventId)
{
    PHW_STREAM_REQUEST_BLOCK_EXT RequestBlock;
    if (NotificationType == DeviceRequestComplete)
    {
        RequestBlock = (PHW_STREAM_REQUEST_BLOCK_EXT)pSrb;

        KeSetEvent(&RequestBlock->Event, 0, FALSE);
        return;
    }

    UNIMPLEMENTED;
}

/*
 *@implemented
 */
PVOID
STREAMAPI
StreamClassGetDmaBuffer(
    IN PVOID  HwDeviceExtension)
{
    PSTREAM_DEVICE_EXTENSION DeviceExtension;

    /* Get our DeviceExtension */
    DeviceExtension = (PSTREAM_DEVICE_EXTENSION) ((ULONG_PTR)HwDeviceExtension - sizeof(STREAM_DEVICE_EXTENSION));
    ASSERT(DeviceExtension->DeviceExtension == HwDeviceExtension);

    return DeviceExtension->DmaCommonBuffer;
}

NTSTATUS
NTAPI
StreamClassRWCompletion(
    IN PDEVICE_OBJECT  DeviceObject,
    IN PIRP  Irp,
    IN PVOID  Context)
{
    PIO_STATUS_BLOCK IoStatusBlock = (PIO_STATUS_BLOCK)Context;

    IoStatusBlock->Information = Irp->IoStatus.Information;
    IoStatusBlock->Status = Irp->IoStatus.Status;

    return STATUS_SUCCESS;
}

/*
 *@implemented
 */
BOOLEAN
STREAMAPI
StreamClassReadWriteConfig(
    IN PVOID  HwDeviceExtension,
    IN BOOLEAN  Read,
    IN PVOID  Buffer,
    IN ULONG  OffSet,
    IN ULONG  Length)
{
    PIRP Irp;
    ULONG MajorFunction;
    KEVENT Event;
    PSTREAM_DEVICE_EXTENSION DeviceExtension;
    LARGE_INTEGER Offset;
    IO_STATUS_BLOCK StatusBlock;
    NTSTATUS Status;

    /* Get our DeviceExtension */
    DeviceExtension = (PSTREAM_DEVICE_EXTENSION) ((ULONG_PTR)HwDeviceExtension - sizeof(STREAM_DEVICE_EXTENSION));
    ASSERT(DeviceExtension->DeviceExtension == HwDeviceExtension);

    if (Read)
    {
        /* Zero input buffer */
        RtlZeroMemory(Buffer, Length);
    }

    /* Set request type */
    MajorFunction = (Read ? IRP_MJ_READ : IRP_MJ_WRITE);

    /* Initialize event */
    KeInitializeEvent(&Event, NotificationEvent, FALSE);

    /* Set offset */
    Offset.QuadPart = OffSet;

    /* Pre-init status block */
    StatusBlock.Status = STATUS_NOT_SUPPORTED;

    /* Create Irp */
    Irp = IoBuildSynchronousFsdRequest(MajorFunction,
                                       DeviceExtension->LowerDeviceObject, /* Verify */
                                       Buffer,
                                       Length,
                                       &Offset,
                                       &Event,
                                       &StatusBlock);

    if (!Irp)
    {
        /* Failed to allocate memory */
        return FALSE;
    }

    /* Setup a completion routine */
    IoSetCompletionRoutine(Irp, StreamClassRWCompletion, (PVOID)&Event, TRUE, TRUE, TRUE);

    /* Call driver */
    Status = IoCallDriver(DeviceExtension->LowerDeviceObject, Irp);

    if (Status == STATUS_PENDING)
    {
        /* Request is pending, wait for result */
        KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL);
        /* Fetch result */
        Status = StatusBlock.Status;
    }

    if (!NT_SUCCESS(Status))
    {
        return FALSE;
    }

    /* FIXME Handle Length != InputLength */
    return TRUE;
}
